package com.sluggard.security.manage.controller;


import cn.hutool.core.bean.BeanUtil;
import com.google.code.kaptcha.impl.DefaultKaptcha;
import com.sluggard.common.exception.BusinessException;
import com.sluggard.common.model.res.Result;
import com.sluggard.common.utils.NetUtil;
import com.sluggard.security.core.manage.CaptchaCacheManage;
import com.sluggard.security.core.manage.SessionManage;
import com.sluggard.security.manage.model.req.LoginReq;
import com.sluggard.security.manage.model.req.TokenReq;
import com.sluggard.security.manage.model.res.UserInfoRes;
import com.sluggard.security.manage.model.vo.SecurityUser;
import com.sluggard.security.properties.SecurityConfigProperties;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid;
import lombok.SneakyThrows;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.util.Base64;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@Tag(name = "用户登录")
@RestController
@RequestMapping("manage/loginout")
public class LoginLogoutController {

    @Resource
    private AuthenticationManager authenticationManager;

    @Resource
    private CaptchaCacheManage captchaCacheManage;

    @Resource
    private SecurityConfigProperties securityConfigProperties;

    @Resource
    private DefaultKaptcha defaultKaptcha;

    @Resource
    HttpServletRequest request;

    @Operation(summary = "获取TOKEN")
    @GetMapping("getToken")
    public Result<Map<Object, Object>> getToken() {
        return Result.okMap("token", UUID.randomUUID().toString().replace("-", ""));
    }

    /**
     * 图片验证码
     *
     * @param token    授权信息可直接使用参数
     * @param response 响应对象
     * @throws Exception
     */
    @Operation(summary = "图片验证码")
    @Parameter(name = "token", description = "token", required = true, in = ParameterIn.QUERY)
    @PostMapping("/captcha")
    public Result<Map<Object, Object>> captcha(@Validated @RequestBody TokenReq tokenReq) {
        String token = tokenReq.token();
        String text = defaultKaptcha.createText();
        if (!StringUtils.isNotBlank(token) && token.length() == 32) {
            throw new BusinessException("token异常");
        }
        captchaCacheManage.saveCaptcha(token, text, securityConfigProperties.getKaptcha().getTimeOut(), TimeUnit.SECONDS);
        //使用生产的验证码字符串返回一个BufferedImage对象并转为byte写入到byte数组中
        BufferedImage challenge = defaultKaptcha.createImage(text);
        return Result.okMap("img", "data:image/png;base64," + bufferedImageToBase64(challenge));
    }


    /**
     * BufferedImage 编码转换为 base64
     *
     * @param image
     * @return
     */
    @SneakyThrows
    private static String bufferedImageToBase64(BufferedImage image) {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ImageIO.write(image, "png", byteArrayOutputStream);
        byte[] imageBytes = byteArrayOutputStream.toByteArray();
        return Base64.getEncoder().encodeToString(imageBytes);
    }


    @Operation(summary = "登录")
    @PostMapping("login")
    public Result<UserInfoRes> login(@Valid @RequestBody LoginReq loginReq) {
        String ipAddress = NetUtil.getIpAddress(request);
        // 验证码校验
        if (securityConfigProperties.getCaptchaEnable()) {
            // 判断是否需要进行验证码校验
            if (captchaCacheManage.getLoginErrorTimes(ipAddress) >= securityConfigProperties.getKaptcha().getErrTimes()) {
                String cacheCaptcha = captchaCacheManage.getCaptcha(loginReq.token());
                if (StringUtils.isBlank(loginReq.captcha())) {
                    throw new BadCredentialsException("验证码不能为空");
                }
                if (!StringUtils.equalsIgnoreCase(cacheCaptcha, loginReq.captcha())) {
                    throw new BadCredentialsException("验证码错误");
                }
            }
        }

        // 登录失败默认+1
        captchaCacheManage.loginErrorTimesIncrement(ipAddress);

        // 执行登录逻辑
        UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(loginReq.username(), loginReq.password());
        Authentication authentication = authenticationManager.authenticate(auth);
        SecurityContextHolder.getContext().setAuthentication(authentication);
        SecurityUser securityUser = (SecurityUser) authentication.getPrincipal();

        // 登录成功直接清除
        captchaCacheManage.clearLoginErrorTimes(ipAddress);
        captchaCacheManage.clearCaptcha(ipAddress);

        // 转化为响应信息
        UserInfoRes userInfo = new UserInfoRes();
        BeanUtil.copyProperties(securityUser, userInfo);

        // 填充Token
        userInfo.setToken(UUID.randomUUID().toString().replace("-", ""));

        SessionManage.setSession(userInfo.getToken(), securityUser);
        return Result.ok(userInfo);
    }

    @Operation(summary = "是否需要登录验证码")
    @GetMapping("needCaptcha")
    public Result<Boolean> needCaptcha() {
        return Result.ok(securityConfigProperties.getCaptchaEnable() &&
                captchaCacheManage.getLoginErrorTimes(NetUtil.getIpAddress(request)) >= securityConfigProperties.getKaptcha().getErrTimes());
    }

    @Operation(summary = "退出用户")
    @PostMapping("logout")
    public Result<Void> logout(@Validated @RequestBody TokenReq tokenReq){
        SessionManage.removeSession(tokenReq.token());
        return Result.ok();
    }
}
